/*-------------------------------------------------------------------------
 *
 * windowfuncs.c
 *	  标准窗口函数在 SQL 规范中定义。
 *
 * Portions Copyright (c) 2000-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/windowfuncs.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "nodes/supportnodes.h"
#include "optimizer/optimizer.h"
#include "utils/builtins.h"
#include "windowapi.h"

/*
 * 排名过程信息
 */
typedef struct rank_context
{
	int64		rank;			/* 当前排名 */
} rank_context;

/*
 * ntile 过程信息
 */
typedef struct
{
	int32		ntile;			/* 当前结果 */
	int64		rows_per_bucket;	/* 当前桶的行号 */
	int64		boundary;		/* 桶中应该有多少行 */
	int64		remainder;		/* (总行数) % (桶数) */
} ntile_context;

static bool fc_rank_up(WindowObject fc_winobj);
static Datum fc_leadlag_common(FunctionCallInfo fcinfo,
							bool fc_forward, bool fc_withoffset, bool fc_withdefault);


/*
 * 用于 *_rank 函数的实用程序例程。
 */
static bool fc_rank_up(WindowObject fc_winobj)
{
	bool		fc_up = false;		/* 排名是否应该增加？ */
	int64		fc_curpos = WinGetCurrentPosition(fc_winobj);
	rank_context *fc_context;

	fc_context = (rank_context *)
		WinGetPartitionLocalMemory(fc_winobj, sizeof(rank_context));

	if (fc_context->rank == 0)
	{
		/* 第一次调用：第一行的排名始终为 1 */
		Assert(fc_curpos == 0);
		fc_context->rank = 1;
	}
	else
	{
		Assert(fc_curpos > 0);
		/* 当前元组和前一个元组是否通过 ORDER BY 子句匹配？ */
		if (!WinRowsArePeers(fc_winobj, fc_curpos - 1, fc_curpos))
			fc_up = true;
	}

	/* 我们可以推进标记，但只能在访问前一行之后 */
	WinSetMarkPosition(fc_winobj, fc_curpos);

	return fc_up;
}


/*
 * row_number
 * 从 1 开始递增，直到当前分区结束。
 */
Datum window_row_number(PG_FUNCTION_ARGS)
{
	WindowObject fc_winobj = PG_WINDOW_OBJECT();
	int64		fc_curpos = WinGetCurrentPosition(fc_winobj);

	WinSetMarkPosition(fc_winobj, fc_curpos);
	PG_RETURN_INT64(fc_curpos + 1);
}

/*
 * window_row_number_support
 *		window_row_number() 的支持函数
 */
Datum window_row_number_support(PG_FUNCTION_ARGS)
{
	Node	   *fc_rawreq = (Node *) PG_GETARG_POINTER(0);

	if (IsA(fc_rawreq, SupportRequestWFuncMonotonic))
	{
		SupportRequestWFuncMonotonic *fc_req = (SupportRequestWFuncMonotonic *) fc_rawreq;

		/* row_number() 是单调递增的 */
		fc_req->monotonic = MONOTONICFUNC_INCREASING;
		PG_RETURN_POINTER(fc_req);
	}

	PG_RETURN_POINTER(NULL);
}

/*
 * rank
 * 当关键列改变时排名发生变化。
 * 新的排名数字是当前行号。
 */
Datum window_rank(PG_FUNCTION_ARGS)
{
	WindowObject fc_winobj = PG_WINDOW_OBJECT();
	rank_context *fc_context;
	bool		fc_up;

	fc_up = fc_rank_up(fc_winobj);
	fc_context = (rank_context *)
		WinGetPartitionLocalMemory(fc_winobj, sizeof(rank_context));
	if (fc_up)
		fc_context->rank = WinGetCurrentPosition(fc_winobj) + 1;

	PG_RETURN_INT64(fc_context->rank);
}

/*
 * window_rank_support
 *		window_rank() 的支持函数
 */
Datum window_rank_support(PG_FUNCTION_ARGS)
{
	Node	   *fc_rawreq = (Node *) PG_GETARG_POINTER(0);

	if (IsA(fc_rawreq, SupportRequestWFuncMonotonic))
	{
		SupportRequestWFuncMonotonic *fc_req = (SupportRequestWFuncMonotonic *) fc_rawreq;

		/* rank() 是单调递增的 */
		fc_req->monotonic = MONOTONICFUNC_INCREASING;
		PG_RETURN_POINTER(fc_req);
	}

	PG_RETURN_POINTER(NULL);
}

/*
 * dense_rank
 * 当关键列改变时，排名增加 1。
 */
Datum window_dense_rank(PG_FUNCTION_ARGS)
{
	WindowObject fc_winobj = PG_WINDOW_OBJECT();
	rank_context *fc_context;
	bool		fc_up;

	fc_up = fc_rank_up(fc_winobj);
	fc_context = (rank_context *)
		WinGetPartitionLocalMemory(fc_winobj, sizeof(rank_context));
	if (fc_up)
		fc_context->rank++;

	PG_RETURN_INT64(fc_context->rank);
}

/*
 * window_dense_rank_support
 *		window_dense_rank() 的支持函数
 */
Datum window_dense_rank_support(PG_FUNCTION_ARGS)
{
	Node	   *fc_rawreq = (Node *) PG_GETARG_POINTER(0);

	if (IsA(fc_rawreq, SupportRequestWFuncMonotonic))
	{
		SupportRequestWFuncMonotonic *fc_req = (SupportRequestWFuncMonotonic *) fc_rawreq;

		/* dense_rank() 是单调递增的 */
		fc_req->monotonic = MONOTONICFUNC_INCREASING;
		PG_RETURN_POINTER(fc_req);
	}

	PG_RETURN_POINTER(NULL);
}

/*
 * percent_rank
 * 返回 0 到 1 之间的分数（包含 0 和 1），
 * 其描述为 (RK - 1) / (NR - 1)，其中 RK 是当前行的
 * 排名，NR 是行的总数，按规范。
 */
Datum window_percent_rank(PG_FUNCTION_ARGS)
{
	WindowObject fc_winobj = PG_WINDOW_OBJECT();
	rank_context *fc_context;
	bool		fc_up;
	int64		fc_totalrows = WinGetPartitionRowCount(fc_winobj);

	Assert(fc_totalrows > 0);

	fc_up = fc_rank_up(fc_winobj);
	fc_context = (rank_context *)
		WinGetPartitionLocalMemory(fc_winobj, sizeof(rank_context));
	if (fc_up)
		fc_context->rank = WinGetCurrentPosition(fc_winobj) + 1;

	/* 如果只有一行，则返回零，按规范 */
	if (fc_totalrows <= 1)
		PG_RETURN_FLOAT8(0.0);

	PG_RETURN_FLOAT8((float8) (fc_context->rank - 1) / (float8) (fc_totalrows - 1));
}

/*
 * cume_dist
 * 返回 0 到 1 之间的分数（包含 0 和 1），
 * 其描述为 NP / NR，其中 NP 是当前行之前或
 * 同行的行数，NR 是行的总数，按规范。
 */
Datum window_cume_dist(PG_FUNCTION_ARGS)
{
	WindowObject fc_winobj = PG_WINDOW_OBJECT();
	rank_context *fc_context;
	bool		fc_up;
	int64		fc_totalrows = WinGetPartitionRowCount(fc_winobj);

	Assert(fc_totalrows > 0);

	fc_up = fc_rank_up(fc_winobj);
	fc_context = (rank_context *)
		WinGetPartitionLocalMemory(fc_winobj, sizeof(rank_context));
	if (fc_up || fc_context->rank == 1)
	{
		/*
		 * 当前行不是前一行的同类，或者只是第一行，
		 * 因此计算与当前行同类的行数。
		 */
		int64		fc_row;

		fc_context->rank = WinGetCurrentPosition(fc_winobj) + 1;

		/*
		 * 从当前 + 1 开始
		 */
		for (fc_row = fc_context->rank; fc_row < fc_totalrows; fc_row++)
		{
			if (!WinRowsArePeers(fc_winobj, fc_row - 1, fc_row))
				break;
			fc_context->rank++;
		}
	}

	PG_RETURN_FLOAT8((float8) fc_context->rank / (float8) fc_totalrows);
}

/*
 * ntile
 * 计算一个精确的数字值，规模为 0（零），
 * 范围从 1（一）到 n，按规范。
 */
Datum window_ntile(PG_FUNCTION_ARGS)
{
	WindowObject fc_winobj = PG_WINDOW_OBJECT();
	ntile_context *fc_context;

	fc_context = (ntile_context *)
		WinGetPartitionLocalMemory(fc_winobj, sizeof(ntile_context));

	if (fc_context->ntile == 0)
	{
		/* 第一次调用 */
		int64		fc_total;
		int32		fc_nbuckets;
		bool		fc_isnull;

		fc_total = WinGetPartitionRowCount(fc_winobj);
		fc_nbuckets = DatumGetInt32(WinGetFuncArgCurrent(fc_winobj, 0, &fc_isnull));

		/*
		 * 根据规格：如果 NT 是空值，则结果是空值。
		 */
		if (fc_isnull)
			PG_RETURN_NULL();

		/*
		 * 根据规格：如果 NT 小于或等于 0（零），则会引发异常条件。
		 */
		if (fc_nbuckets <= 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_ARGUMENT_FOR_NTILE),
					 errmsg("argument of ntile must be greater than zero")));

		fc_context->ntile = 1;
		fc_context->rows_per_bucket = 0;
		fc_context->boundary = fc_total / fc_nbuckets;
		if (fc_context->boundary <= 0)
			fc_context->boundary = 1;
		else
		{
			/*
			 * 如果总数无法整除，则在领先桶中添加 1 行。
			 */
			fc_context->remainder = fc_total % fc_nbuckets;
			if (fc_context->remainder != 0)
				fc_context->boundary++;
		}
	}

	fc_context->rows_per_bucket++;
	if (fc_context->boundary < fc_context->rows_per_bucket)
	{
		/* ntile up */
		if (fc_context->remainder != 0 && fc_context->ntile == fc_context->remainder)
		{
			fc_context->remainder = 0;
			fc_context->boundary -= 1;
		}
		fc_context->ntile += 1;
		fc_context->rows_per_bucket = 1;
	}

	PG_RETURN_INT32(fc_context->ntile);
}

/*
 * leadlag_common
 * lead() 和 lag() 的公共操作
 * 对于 lead()，前进为真，而对于 lag()，为假。
 * withoffset 表示我们有一个偏移的第二个参数。
 * withdefault 表示我们有一个默认的第三个参数。
 */
static Datum fc_leadlag_common(FunctionCallInfo fcinfo,
			   bool fc_forward, bool fc_withoffset, bool fc_withdefault)
{
	WindowObject fc_winobj = PG_WINDOW_OBJECT();
	int32		fc_offset;
	bool		fc_const_offset;
	Datum		fc_result;
	bool		fc_isnull;
	bool		fc_isout;

	if (fc_withoffset)
	{
		fc_offset = DatumGetInt32(WinGetFuncArgCurrent(fc_winobj, 1, &fc_isnull));
		if (fc_isnull)
			PG_RETURN_NULL();
		fc_const_offset = get_fn_expr_arg_stable(fcinfo->flinfo, 1);
	}
	else
	{
		fc_offset = 1;
		fc_const_offset = true;
	}

	fc_result = WinGetFuncArgInPartition(fc_winobj, 0,
									  (fc_forward ? fc_offset : -fc_offset),
									  WINDOW_SEEK_CURRENT,
									  fc_const_offset,
									  &fc_isnull, &fc_isout);

	if (fc_isout)
	{
		/*
		 * 目标行超出分区；如果提供默认值，则提供默认值。否则将保持 NULL
		 */
		if (fc_withdefault)
			fc_result = WinGetFuncArgCurrent(fc_winobj, 2, &fc_isnull);
	}

	if (fc_isnull)
		PG_RETURN_NULL();

	PG_RETURN_DATUM(fc_result);
}

/*
 * lag
 * 返回在当前行之前 1 行内分区评估的 VE 值，
 * 根据规格。
 */
Datum window_lag(PG_FUNCTION_ARGS)
{
	return fc_leadlag_common(fcinfo, false, false, false);
}

/*
 * lag_with_offset
 * 返回在当前行之前 OFFSET 行内分区评估的 VE 值，
 * 根据规格。
 */
Datum window_lag_with_offset(PG_FUNCTION_ARGS)
{
	return fc_leadlag_common(fcinfo, false, true, false);
}

/*
 * lag_with_offset_and_default
 * 与 lag_with_offset 相同，但接受默认值
 * 作为其第三个参数。
 */
Datum window_lag_with_offset_and_default(PG_FUNCTION_ARGS)
{
	return fc_leadlag_common(fcinfo, false, true, true);
}

/*
 * lead
 * 返回在当前行之后 1 行内分区评估的 VE 值，
 * 根据规格。
 */
Datum window_lead(PG_FUNCTION_ARGS)
{
	return fc_leadlag_common(fcinfo, true, false, false);
}

/*
 * lead_with_offset
 * 返回在当前行之后 OFFSET 行数内分区评估的 VE 值，
 * 根据规格。
 */
Datum window_lead_with_offset(PG_FUNCTION_ARGS)
{
	return fc_leadlag_common(fcinfo, true, true, false);
}

/*
 * lead_with_offset_and_default
 * 与 lead_with_offset 相同，但接受默认值
 * 作为其第三个参数。
 */
Datum window_lead_with_offset_and_default(PG_FUNCTION_ARGS)
{
	return fc_leadlag_common(fcinfo, true, true, true);
}

/*
 * first_value
 * 返回在窗口框架的第一行评估的 VE 值，根据规格。
 */
Datum window_first_value(PG_FUNCTION_ARGS)
{
	WindowObject fc_winobj = PG_WINDOW_OBJECT();
	Datum		fc_result;
	bool		fc_isnull;

	fc_result = WinGetFuncArgInFrame(fc_winobj, 0,
								  0, WINDOW_SEEK_HEAD, true,
								  &fc_isnull, NULL);
	if (fc_isnull)
		PG_RETURN_NULL();

	PG_RETURN_DATUM(fc_result);
}

/*
 * last_value
 * 返回在窗口框架的最后一行评估的 VE 值，根据规格。
 */
Datum window_last_value(PG_FUNCTION_ARGS)
{
	WindowObject fc_winobj = PG_WINDOW_OBJECT();
	Datum		fc_result;
	bool		fc_isnull;

	fc_result = WinGetFuncArgInFrame(fc_winobj, 0,
								  0, WINDOW_SEEK_TAIL, true,
								  &fc_isnull, NULL);
	if (fc_isnull)
		PG_RETURN_NULL();

	PG_RETURN_DATUM(fc_result);
}

/*
 * nth_value
 * 返回在窗口框架的第一行的第 n 行评估的 VE 值，根据规格。
 */
Datum window_nth_value(PG_FUNCTION_ARGS)
{
	WindowObject fc_winobj = PG_WINDOW_OBJECT();
	bool		fc_const_offset;
	Datum		fc_result;
	bool		fc_isnull;
	int32		fc_nth;

	fc_nth = DatumGetInt32(WinGetFuncArgCurrent(fc_winobj, 1, &fc_isnull));
	if (fc_isnull)
		PG_RETURN_NULL();
	fc_const_offset = get_fn_expr_arg_stable(fcinfo->flinfo, 1);

	if (fc_nth <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_ARGUMENT_FOR_NTH_VALUE),
				 errmsg("argument of nth_value must be greater than zero")));

	fc_result = WinGetFuncArgInFrame(fc_winobj, 0,
								  fc_nth - 1, WINDOW_SEEK_HEAD, fc_const_offset,
								  &fc_isnull, NULL);
	if (fc_isnull)
		PG_RETURN_NULL();

	PG_RETURN_DATUM(fc_result);
}
